﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using System.IO;

namespace Assets.Scripts.LDS
{
    sealed class ScriptProcessor
    {

        static private ScriptProcessor _instance = null;

        public static ScriptProcessor Instance
        {
            get { return _instance ?? (_instance = new ScriptProcessor()); }
        }

        public bool LDSEnable = false;

        private ScriptProcessor()
        {
            // clear the result before
            clearResult();
            //Scene.Instance.loadScene("331.result");
        }


        // input script output result
        // @param script: the script need to process
        // @return SGA.result: the result file contains the scene and action information in the script
        public void Process(string script)
        {
            /************************************************/
            // Change Scene Input
            SceneController sc = GameObject.Find("Controller").GetComponent<SceneController>();
            if (!string.IsNullOrEmpty(sc.sceceText) && script.Contains("$"))
            {
                script = sc.sceceText;
            }
            /*************************************************/

            cleanText(ref script);

            string scene_result = "", action_result = "";

            if (script.StartsWith("camera "))
            {
                string cameraName = script.Substring(7);

                GameObject.Find("Anubis/CM ClearShot1").SendMessage("changeCamera", cameraName);
                return;

            }
            else if (script.StartsWith("clear"))
            {
                Scene.Instance.Clear();

                // SIGGRAPH_Asia_2019.ClearModels();
                ModelsManager.ClearModels();

                TextDialog.textDebug("clear!");
            }
            else if (script.StartsWith("delete "))
            {
                string objectName = script.Substring(7);

                Scene.Instance.DeleteObject(objectName);

                //SIGGRAPH_Asia_2019.RemoveModel(objectName);
            }
            else if (script.StartsWith("sky "))
            {
                string skyname = script.Substring(4);

                GameObject.Find("Anubis/CM ClearShot1").SendMessage("ChangeSkybox", skyname);

                return;
            }
            else if (script.StartsWith("wea "))
            {
                string weathername = script.Substring(4);

                GameObject.Find("Anubis/CM ClearShot1").SendMessage("ChangeWeather", weathername);
                return;

            }
            else if (script.StartsWith("ls "))
            {
                string filename = script.Substring(3);

                Scene.Instance.loadScene(filename);
            }
            else if (script.StartsWith("ss "))
            {
                string filename = script.Substring(3);

                Scene.Instance.saveScene(filename);

                return;
            }
            else
            {
                //Script -> NLP
                bool hasScene = RunNLP(script);

                // NLP(motion) -> motion.result
                action_result = GetMotion();
                //TextDialog.textDebug(action_result);

                // NLP(scene) -> scene.result
                if (hasScene)
                {
                    Scene.Instance.runOneEvolutionStep();// scene.txt -> scene.result
                }
            }

            if (LDSEnable) scene_result = Scene.Instance.GetScene();

            // Write SGA.result
            using (StreamWriter sw = new StreamWriter(Params.interfaceFile))
            {
                sw.Write(scene_result + action_result);
            }

            // SIGGRAPH_Asia_2019.OnChanged();
            ProcessText.OnChanged();
        }

        /// <summary>
        /// Remove meaningless characters
        /// </summary>
        /// <param name="input"></param>
        private static void cleanText(ref string input)
        {
            input = input.Replace("\n", ""); // Remove "\n"
            input = input.Replace("\r", ""); // Remove "\r"
        }

        // deal with the script, generate format out-motion.txt and out-scene.txt
        // @param string: the script need to process
        // @return bool: the script has scene?
        // @output: out-motion.txt and out-scene.txt
        // There should be no extra "." in a sentence, or it will be recognized as two sentences
        // Distinguish environment and action statement with $
        static bool RunNLP(string input)
        {
            string scene_str = "", motion_str = "";

            // Split by full stop, then sort by $
            foreach (string sentence in input.Split('.'))
            {
                if (sentence == "") continue;
                if (sentence.Contains("$"))
                {
                    scene_str += sentence.Replace("$", "") + '.';
                }
                else
                    motion_str += sentence + '.';
            }

            // Preprocess the action statement
            ProcessMotionSentence(ref motion_str);

            //TextDialog.textDebug(scene_str);
            //TextDialog.textDebug(motion_str);

            System.Diagnostics.Process process = new System.Diagnostics.Process();
            process.StartInfo.FileName = @"matt-SEL-playground.exe";
            process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();


            using (StreamWriter sw = new StreamWriter("in.txt"))
            {
                sw.Write(motion_str);
            }
            process.Start();
            //process.WaitForExit();

            stopwatch.Start(); // Start to Monitor runtime
            while (!process.HasExited)
            {
                if (stopwatch.Elapsed.TotalSeconds > 5)
                {
                    TextDialog.textDebug("NLP server doesn't start!!!", true);
                    process.Kill();
                    return false;
                }
            }

            File.Copy("out.txt", "out-motion.txt", true);


            using (StreamWriter sw = new StreamWriter("in.txt"))
            {
                sw.Write(scene_str);
            }
            process.Start();
            process.WaitForExit();
            File.Copy("out.txt", "out-scene.txt", true);

            return scene_str != "";
        }


        //Preprocess the action statement to make NLP recognize better
        //Convert three orders into continuous tense
        //static private Dictionary<string, string> now2doing = new Dictionary<string, string> { {"stands","standing"},{ "turns", "turning" }, { "carries", "carrying" }, { "walks", "walking" }, { "runs", "running" }, { "climbs", "climbing" }, { "sits", "sitting" }, { "opens", "opening" } };
        static private void ProcessMotionSentence(ref string motion_str)
        {
            motion_str = motion_str.Replace("looks", "turns");

            //foreach (var verb in now2doing) //It is convenient to analyze Adverbs by converting the general present tense into the present continuous tense
            //{
            //    if (motion_str.Contains(verb.Key))
            //    {
            //        motion_str = motion_str.Replace(verb.Key, "is" + " " + verb.Value);
            //    }
            //}
        }

        // input：out-motion.txt
        // @return string: action txt
        // Handle the exception to ensure that the output actioncount is correct and the action meets the requirements
        // Action Format:
        // <subject> <act> <object> (from <target>) to/in/on <target>
        // <act>needs to be three orders or continuous，<adv>effective:forward, left, right
        //static private Dictionary<string, string> doing2normal = new Dictionary<string, string> {{"standing","stand"}, { "turning", "turn" }, { "carrying", "carry" }, { "walking", "walk" }, { "running", "run" }, { "climbing", "climb" }, { "sitting", "sit" }, { "opening", "open" } };// 将现在进行时转换成标准型
        static private string GetMotion()
        {
            //static private Dictionary<string, string> now2doing = new Dictionary<string, string> { {"stands","standing"},{ "turns", "turning" }, { "carries", "carrying" }, { "walks", "walking" }, { "runs", "running" }, { "climbs", "climbing" }, { "sits", "sitting" }, { "opens", "opening" } };
            Dictionary<string, string> motiondic = new Dictionary<string, string> { { "stands", "stand" }, { "turns", "turn" }, { "carries", "carry" }, { "walks", "walk" }, { "runs", "run" }, { "climbs", "climb" }, { "sits", "sit" }, { "opens", "open" } };

            // HashSet<string> prep = new HashSet<string> { "from", "to", "on", "in", "at"};
            HashSet<string> prep = new HashSet<string> { "from", "to", "on", "in", "at", "with" };

            // the filename can be changed
            List<SelSentence> actions = TextSemGraphManager.ReadSentences("out-motion.txt");

            int actionCount = 0;
            StringBuilder actions_str = new StringBuilder();// write to a string 
            using (StringWriter sw = new StringWriter(actions_str))
            {
                // The entity of the sentence is processed, and the correct sentence is output and counted
                for (int i = 0; i < actions.Count; ++i)
                {

                    if (actions[i].commandCount > 0)
                    {
                        // ! Command Format

                        // Format:
                        // <subject> <act> (<adv>) <object> (from <target>) to/in/on/at <target>
                        SelCommand action_entity;
                        string act;

                        action_entity = actions[i].m_commands[0];
                        act = action_entity.verbString;


                        SelCommand action_entity2 = null;
                        if (actions[i].m_commands.Count > 1)
                        {
                            action_entity2 = actions[i].m_commands[1];
                        }

                        if (action_entity2 != null)
                        {
                            // has style motion

                            string adv = action_entity2.verbString;
                            adv = " " + adv + "ly";

                            // Exception handling: judge whether the verb is correct or not
                            if (motiondic.ContainsValue(act))
                            {
                                //  <act> (<adv>) 

                                sw.WriteLine("action" + " " + actionCount + " " + act + adv);

                                // subject
                                sw.WriteLine("subject" + " " + "man" + " " + "1");

                                // object
                                bool isModel = false;
                                sw.WriteLine("model null");

                                // target

                                List<string> targets = new List<string>();
                                string styletarget = "";

                                foreach (var relation in action_entity.targetStrings)
                                {
                                    string objName = relation.Split('|').Last();
                                    targets.Add(objName);
                                }

                                foreach (var relation in action_entity2.targetStrings)
                                {
                                    string objName = relation.Split('|').Last();
                                    styletarget = (objName);
                                }


                                sw.WriteLine("targetCount " + targets.Count);
                                for (int temp = 0; temp < targets.Count; ++temp)
                                {
                                    sw.WriteLine(targets[temp].Split('-')[0] + " " + targets[temp].Split('-')[1]);
                                }


                                if (styletarget != "" && adv != " null")
                                {
                                    sw.WriteLine("styleTargetCount " + 1);
                                    sw.WriteLine(styletarget.Split('-')[0] + " " + styletarget.Split('-')[1]);

                                }
                                else sw.WriteLine("styleTargetCount " + 0);

                                actionCount++;
                            }
                            else
                            {
                                // If the action is not correct, an error will be reported
                                TextDialog.textDebug("!Action can not detected: " + act);
                            }
                        }
                        else
                        {
                            // has no style motion

                            string adv = " null";
                            if (action_entity.attributeCount > 0)
                            {//     | means there are adverbs
                                adv = " " + action_entity.attributeStrings[0];
                            }

                            // Exception handling: judge whether the verb is correct or not
                            if (motiondic.ContainsValue(act))
                            {
                                //  <act> (<adv>) 

                                sw.WriteLine("action" + " " + actionCount + " " + act + adv);

                                // subject
                                sw.WriteLine("subject" + " " + "man" + " " + "1");

                                // object
                                bool isModel = false;
                                sw.WriteLine("model null");

                                // target

                                List<string> targets = new List<string>();
                                string styletarget = "";

                                foreach (var relation in action_entity.targetStrings)
                                {
                                    string objName = relation.Split('|').Last();
                                    targets.Add(objName);
                                }

                                sw.WriteLine("targetCount " + targets.Count);
                                for (int temp = 0; temp < targets.Count; ++temp)
                                {
                                    sw.WriteLine(targets[temp].Split('-')[0] + " " + targets[temp].Split('-')[1]);
                                }


                                sw.WriteLine("styleTargetCount " + 0);

                                actionCount++;
                            }
                            else
                            {
                                //If the action is not correct, an error will be reported
                                TextDialog.textDebug("!Action can not detected: " + act);
                            }
                        }
                    }
                    else if (actions[i].entityCount > 0)
                    {

                        // Format:
                        // <subject> <act> (<adv>) <object> (from <target>) to/in/on/at <target>
                        SelEntity action_entity;
                        string act;

                        // entity[0]
                        try
                        {
                            // verb is in entity[0].attributes
                            action_entity = actions[i].m_entities[0];
                            act = action_entity.m_attributes[0].nameString;
                        }
                        catch
                        {
                            TextDialog.textDebug("!Sentence Format Error:" + actions[i].textString);
                            continue;
                        }

                        SelEntity action_entity2 = null;
                        for (int j = 1; j < actions[i].m_entities.Count; ++j)
                        {
                            string nameMan2 = actions[i].m_entities[j].nameString.Split('-')[0];
                            string nameMan1 = action_entity.nameString.Split('-')[0];
                            if (nameMan1 == nameMan2)
                            {
                                action_entity2 = actions[i].m_entities[j];
                                if (action_entity2.attributeCount < 1)
                                {
                                    TextDialog.textDebug("Style Action can not detected!", true);
                                    action_entity2 = null;
                                }
                            }
                        }

                        if (action_entity2 != null)
                        {
                            string adv = action_entity2.m_attributes[0].nameString;
                            adv = " " + adv.Substring(0, adv.Length - 1) + "ly";

                            // Exception handling: judge whether the verb is correct or not
                            if (motiondic.ContainsKey(act))
                            {
                                //  <act> (<adv>) 

                                sw.WriteLine("action" + " " + actionCount + " " + motiondic[act] + adv);

                                // subject
                                sw.WriteLine("subject" + " " + action_entity.nameString.Split('-')[0] + " " + action_entity.nameString.Split('-')[1]);

                                // object
                                bool isModel = false;
                                foreach (var relation in action_entity.m_relationships)
                                    if (relation.nameString == act)
                                    {
                                        sw.WriteLine("model " + relation.entityString.Split('-')[0] + " " + relation.entityString.Split('-')[1]);
                                        isModel = true;
                                    }
                                if (!isModel)
                                    sw.WriteLine("model null");

                                // target

                                List<string> targets = new List<string>();
                                string styletarget = "";

                                foreach (var relation in action_entity.m_relationships)
                                {
                                    string laststr = relation.nameString.Split(' ').Last();
                                    if (prep.Contains(laststr))
                                    {
                                        //if (laststr == "with") styletarget = relation.entityString;
                                        targets.Add(relation.entityString);
                                    }

                                }

                                foreach (var relation in action_entity2.m_relationships)
                                {
                                    string laststr = relation.nameString.Split(' ').Last();
                                    styletarget = relation.entityString;

                                }

                                sw.WriteLine("targetCount " + targets.Count);
                                for (int temp = 0; temp < targets.Count; ++temp)
                                {
                                    sw.WriteLine(targets[temp].Split('-')[0] + " " + targets[temp].Split('-')[1]);
                                }


                                if (styletarget != "" && adv != " null")
                                {
                                    sw.WriteLine("styleTargetCount " + 1);
                                    sw.WriteLine(styletarget.Split('-')[0] + " " + styletarget.Split('-')[1]);

                                }
                                else sw.WriteLine("styleTargetCount " + 0);

                                actionCount++;
                            }
                            else
                            {
                                // If the action is not correct, an error will be reported
                                TextDialog.textDebug("!Action can not detected: " + act);
                            }
                        }
                        else
                        {
                            string adv = " null";
                            if (act.Contains("|"))
                            {//     | means there are adverbs
                                adv = " " + act.Split('|')[1];
                                act = act.Split('|')[0];
                            }
                            // Exception handling: judge whether the verb is correct or not
                            if (motiondic.ContainsKey(act))
                            {
                                //  <act> (<adv>) 

                                sw.WriteLine("action" + " " + actionCount + " " + motiondic[act] + adv);

                                // subject
                                sw.WriteLine("subject" + " " + action_entity.nameString.Split('-')[0] + " " + action_entity.nameString.Split('-')[1]);

                                // object
                                bool isModel = false;
                                foreach (var relation in action_entity.m_relationships)
                                    if (relation.nameString == act)
                                    {
                                        sw.WriteLine("model " + relation.entityString.Split('-')[0] + " " + relation.entityString.Split('-')[1]);
                                        isModel = true;
                                    }
                                if (!isModel)
                                    sw.WriteLine("model null");


                                List<string> targets = new List<string>();
                                string styletarget = "";

                                foreach (var relation in action_entity.m_relationships)
                                {
                                    string laststr = relation.nameString.Split(' ').Last();
                                    if (prep.Contains(laststr))
                                    {
                                        if (laststr == "with") styletarget = relation.entityString;
                                        else targets.Add(relation.entityString);
                                    }

                                }

                                sw.WriteLine("targetCount " + targets.Count);
                                for (int temp = 0; temp < targets.Count; ++temp)
                                {
                                    sw.WriteLine(targets[temp].Split('-')[0] + " " + targets[temp].Split('-')[1]);
                                }


                                if (styletarget != "" && adv != " null")
                                {
                                    sw.WriteLine("styleTargetCount " + 1);
                                    sw.WriteLine(styletarget.Split('-')[0] + " " + styletarget.Split('-')[1]);

                                }
                                else sw.WriteLine("styleTargetCount " + 0);

                                actionCount++;
                            }
                            else
                            {
                                // If the action is not correct, an error will be reported
                                TextDialog.textDebug("!Action can not detected: " + act);
                            }
                        }
                    }
                    else
                    {
                        TextDialog.textDebug("Motion Format Error!", true);
                    }

                }
            }

            string count_str = string.Format("actionCount:{0}\n", actionCount);
            return count_str + actions_str.ToString();
        }

        public void clearResult()
        {
            using (StreamWriter sw = new StreamWriter("SGA.result"))
            {
            }
        }
    }
}
